package edu.neu.swc.commerce.service.impl;

import com.alibaba.fastjson.JSON;
import edu.neu.swc.commerce.common.TableId;
import edu.neu.swc.commerce.constant.GoodsConstant;
import edu.neu.swc.commerce.dao.EcommerceGoodsDao;
import edu.neu.swc.commerce.entity.EcommerceGoods;
import edu.neu.swc.commerce.goods.DeductGoodsInventory;
import edu.neu.swc.commerce.goods.GoodsInfo;
import edu.neu.swc.commerce.goods.SimpleGoodsInfo;
import edu.neu.swc.commerce.service.IGoodsService;
import edu.neu.swc.commerce.vo.PageSimpleGoodsInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <h1>商品微服务相关服务功能实现</h1>
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodsServiceImpl implements IGoodsService {

    private final StringRedisTemplate redisTemplate;

    private final EcommerceGoodsDao ecommerceGoodsDao;

    public GoodsServiceImpl(StringRedisTemplate redisTemplate, EcommerceGoodsDao ecommerceGoodsDao) {
        this.redisTemplate = redisTemplate;
        this.ecommerceGoodsDao = ecommerceGoodsDao;
    }

    @Override
    public List<GoodsInfo> getGoodsInfoByTableId(TableId tableId) {

        //详细商品信息不能从Redis Cache中去拿，需要从数据库
        List<Long> ids = tableId.getIds().stream()
                .map(TableId.Id::getId)
                .collect(Collectors.toList());

        log.info("get goods info by ids:[{}]", JSON.toJSONString(
                ids
        ));

        List<EcommerceGoods> ecommerceGoods = IterableUtils.toList(
                ecommerceGoodsDao.findAllById(ids)
        );

        return ecommerceGoods.stream()
                .map(EcommerceGoods::toGoodsInfo)
                .collect(Collectors.toList());
    }

    @Override
    public PageSimpleGoodsInfo getSimpleGoodsInfoByPage(int page) {
        //分页不能从Redis Cache中取得

        if(page <= 1){
            page = 1; // 默认是第一页
        }

        // 这里分页的规则（可以自由修改）：一页包含10条数据，按照商品ID倒序排列
        Pageable pageable = PageRequest.of(
                page - 1,
                10,
                Sort.by("id").descending()
        );

        Page<EcommerceGoods> orderPage = ecommerceGoodsDao.findAll(pageable);

        //是否还有更多页: 总页数是否大于当前给定的页
        boolean hasMore = orderPage.getTotalPages() > page;
        return new PageSimpleGoodsInfo(
                orderPage.getContent().stream()
                        .map(EcommerceGoods::toSimple)
                        .collect(Collectors.toList()),
                hasMore
        );
    }

    /**
     * <h2>获取商品简单信息</h2>
     */
    @Override
    public List<SimpleGoodsInfo> getSimpleGoodsInfoByTableId(TableId tableId) {

        //可以从Cache中获取，如果获取不到，可以从DB中获取
        //Redis中的KV都是字符串类型
        List<Object> goodsIds = tableId.getIds().stream()
                .map(i -> i.getId().toString())
                .collect(Collectors.toList());

        // FIXME 如果Cache中查不到GoodsId对应的数据，redisTemplate 返回的是 null
        List<Object> cachedSimpleGoodsInfos = redisTemplate.opsForHash()
                .multiGet(GoodsConstant.ECOMMERCE_GOODS_DICT_KEY, goodsIds)
                .stream().filter(Objects::nonNull)
                .collect(Collectors.toList());

        //如果从redis中查到了商品信息，分两种情况去操作
        if(CollectionUtils.isNotEmpty(cachedSimpleGoodsInfos)){
            //1.如果从缓存中查到了所有需要的SimpleGoodsInfo
            if(cachedSimpleGoodsInfos.size() == goodsIds.size()){
                log.info("get simple goods info by ids from cache:[{}]",
                        JSON.toJSONString(goodsIds));
                return parseCachedGoodsInfo(cachedSimpleGoodsInfos);
            }else{
                //2, 一半从数据表中获取(right)，一半从Redis中获取(left)
                List<SimpleGoodsInfo> left = parseCachedGoodsInfo(cachedSimpleGoodsInfos);

                //曲差集，传递进来的参数减去缓存中查到的，就是缓存中没有的
                Collection<Long> subtractIds = CollectionUtils.subtract(
                        goodsIds.stream()
                                .map(g -> Long.valueOf(g.toString()))
                                .collect(Collectors.toList()),
                        left.stream()
                                .map(SimpleGoodsInfo::getId)
                                .collect(Collectors.toList())
                );

                //缓存中没有的，去查询数据表并缓存
                List<SimpleGoodsInfo> right = queryGoodsInfoFromDBAndCacheToRedis(
                        new TableId(
                                subtractIds.stream()
                                        .map(TableId.Id::new)
                                        .collect(Collectors.toList())
                        )
                );
                //合并left和right并返回
                log.info("get simple goods info by ids (from db and cache) :[{}]",JSON.toJSONString(
                        subtractIds
                ));

                return new ArrayList<>(CollectionUtils.union(left,right));
            }
        }else{
            //从redis里没有查到
            return queryGoodsInfoFromDBAndCacheToRedis(tableId);
        }
    }

    /**
     * <h2>将缓存中的数据反序列化为Java中的 Pojo 对象</h2>
     */
    private List<SimpleGoodsInfo> parseCachedGoodsInfo(List<Object> cachedSimpleGoodsInfo){
        return cachedSimpleGoodsInfo.stream()
                .map(s -> JSON.parseObject(s.toString(), SimpleGoodsInfo.class))
                .collect(Collectors.toList());
    }

    /**
     * <h2>从数据表中查询数据，并缓存到Redis中</h2>
     */
    private List<SimpleGoodsInfo> queryGoodsInfoFromDBAndCacheToRedis(TableId tableId){


        //从数据表中查询数据并做转换
        List<Long> ids = tableId.getIds().stream()
                .map(TableId.Id::getId)
                .collect(Collectors.toList());

        log.info("get simple goods info bu ids:[{}] (from db)",
                JSON.toJSONString(ids));

        List<EcommerceGoods> ecommerceGoods = IterableUtils.toList(
                ecommerceGoodsDao.findAllById(ids)
        );

        List<SimpleGoodsInfo> result = ecommerceGoods.stream()
                .map(EcommerceGoods::toSimple)
                .collect(Collectors.toList());

        //将结果缓存，下次可以直接从 Redis 中查询
        log.info("cache goods info:[{}]", JSON.toJSONString(ids));

        Map<String, String> id2JsonObject = new HashMap<>(result.size());

        result.forEach(g -> id2JsonObject.put(
                g.getId().toString(),JSON.toJSONString(g)
        ));

        //保存到redis中
        redisTemplate.opsForHash().putAll(
                GoodsConstant.ECOMMERCE_GOODS_DICT_KEY, id2JsonObject);

        return result;
    }

    @Override
    public Boolean deductGoodsInventory(List<DeductGoodsInventory> deductGoodsInventories) {

        //检验参数是否合法
        deductGoodsInventories.forEach(d -> {
            if(d.getCount() <= 0){
                throw new RuntimeException("purchase goods count need > 0");
            }
        });

        List<EcommerceGoods> ecommerceGoods = IterableUtils.toList(
                ecommerceGoodsDao.findAllById(
                        deductGoodsInventories.stream()
                                .map(DeductGoodsInventory::getGoodsId)
                                .collect(Collectors.toList())
                )
        );

        //根据传递的goodsIds查不到商品对象，抛出异常
        if(CollectionUtils.isEmpty(ecommerceGoods)){
            throw new RuntimeException("can not found any goods by request");
        }

        if(ecommerceGoods.size() != deductGoodsInventories.size()){
            throw new RuntimeException("request not valid");
        }

        //goods id
        Map<Long,DeductGoodsInventory> goodsId2Inventory =
                deductGoodsInventories.stream().collect(
                        Collectors.toMap(
                                DeductGoodsInventory::getGoodsId,
                                Function.identity())
                );

        //检查是否可以扣减库存，再去扣减库存
        ecommerceGoods.forEach(g -> {
            Long currentInventory = g.getInventory();
            Integer needDeductInventory = goodsId2Inventory.get(g.getId()).getCount();
            if(currentInventory < needDeductInventory){
                log.error("goods inventory is not enough:[{}],[{}]",
                        currentInventory,needDeductInventory);
                throw new RuntimeException("goods Inventory is not enough: " + g.getId());
            }
            //扣减库存
            g.setInventory(currentInventory - needDeductInventory);
            log.info("deduct goods Inventory:[{}],[{}],[{}]",
                    g.getId(),currentInventory,g.getInventory());
        });

        ecommerceGoodsDao.saveAll(ecommerceGoods);
        log.info("deduct goods inventory done");
        return true;
    }
}
